package com.yizisu.basemvvm.utils

import android.os.Environment
import com.yizisu.basemvvm.app
import okhttp3.*
import okio.*
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.util.concurrent.TimeUnit


/**
 * 若红楼梦空，亦初心不变
 * 作者：thinker
 * 包名：com.yizisu.basemvvm.utils
 * 时间：2019/6/27 10:56
 * 描述：
 */
class DownloadHelper {
    data class DownloadBean(
        val fileName: String,
        val downloadUrl: String,
        val file: File,
        val savePath: String,
        val size: Long,
        val isNewDownloadFile: Boolean/*是新下载还是之前的*/
    )

    /**
     * 文件路径，不带文件名
     */
    private var fileType: String = Environment.DIRECTORY_DOWNLOADS
    private var midPath: String? = null

    /**
     * 默认路径
     * 安卓10，沙盒系统，不支持自定义文件夹
     */
    private fun getPath(): String {
        val file = app.getExternalFilesDir(fileType)
        return if (midPath.isNullOrEmpty()) {
            file!!.path
        } else {
            file!!.path.trimEnd('/') + "/" + midPath!!.trimStart('/')
        }
    }

    /**
     * 文件存在的时候，是否覆盖
     */
    private var isOverlayFile = false

    /**
     * 设置存储路径，不要带上文件名
     * @param type The type of files directory to return. May be {@code null}
     *            for the root of the files directory or one of the following
     *            constants for a subdirectory:
     *            {@link android.os.Environment#DIRECTORY_MUSIC},
     *            {@link android.os.Environment#DIRECTORY_PODCASTS},
     *            {@link android.os.Environment#DIRECTORY_RINGTONES},
     *            {@link android.os.Environment#DIRECTORY_ALARMS},
     *            {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
     *            {@link android.os.Environment#DIRECTORY_PICTURES}, or
     *            {@link android.os.Environment#DIRECTORY_MOVIES}.
     */
    fun setPathType(type: String, midPath: String? = null): DownloadHelper {
        fileType = type
        this.midPath = midPath
        return this
    }

    /**
     * 文件存在的时候是否继续下载替换旧文件
     */
    fun setOverlayFile(overlayFile: Boolean): DownloadHelper {
        isOverlayFile = overlayFile
        return this
    }

    /**
     * 设置存储路径，不要带上文件名
     */
//    fun setSimpleSavePath(path: String): DownloadHelper {
//        filePath = Environment.getExternalStorageDirectory().absolutePath + "/" + path.trimStart('/')
//        return this
//    }

    private lateinit var downloadTask: AsyncThread<String, Long, DownloadBean?>
    private var downloadCall: Call? = null
    fun start(url: String, fileName: String, call: DownListener) {
        downloadTask = AsyncThread(call = call) {
            startDownload(it, fileName, url)
        }
        downloadTask.start(/*filePath ?:*/ getPath())
    }

    fun cancel() {
        downloadCall?.cancel()
        downloadTask.cancel(true)
    }


    /**
     * 开始下载线程
     */
    //文件名需要过滤掉这些字符,不然创建失败
    private val nameFiflt = listOf("?", "*", ":", "\\", "/", "|")

    /**
     * 下载的文件
     */
    private var _downloadFile: File? = null
    val downloadFile: File?
        get() = _downloadFile

    /**
     * 删除下载的文件
     */
    fun deleteDownloadFile() {
        if (downloadFile?.exists() == true) {
            downloadFile?.delete()
        }
    }

    private fun startDownload(
        params: Array<out String>,
        fileName: String,
        url: String
    ): DownloadBean? {
        var fos: FileOutputStream? = null
        var byteStream: InputStream? = null
        val dir = params[0]
        val dirFile = File(dir)
        if (!dirFile.exists()) {
            dirFile.mkdirs()
        }
        var fileNameTemp = fileName.trimStart('/')
        fileNameTemp = fileNameTemp.replace(" ", "")
        nameFiflt.forEach {
            fileNameTemp = fileNameTemp.replace(it, "_")
        }
        val path = dir.trimEnd('/') + "/" + fileNameTemp
        val file = File(path)
        _downloadFile = file
        try {
            if (file.exists()) {
                if (isOverlayFile) {
                    file.delete()
                } else {
                    return DownloadBean(fileName, url, file, path, file.length(), false)
                }
            }
            val client = OkHttpClient.Builder()
                .readTimeout(5, TimeUnit.MINUTES)
                .writeTimeout(5, TimeUnit.MINUTES)
                .connectTimeout(5, TimeUnit.MINUTES)
                .addNetworkInterceptor { chain ->
                    val originalResponse = chain.proceed(chain.request())
                    originalResponse.newBuilder()
                        .body(ProgressResponseBody(originalResponse.body()!!) {
                            downloadTask.onError(it)
                            cancel()
                        })
                        .build()
                }
                .build()
            val downRequest = Request.Builder()
                .url(url)
                .build()
            downloadCall = client.newCall(downRequest)
            val response = downloadCall?.execute()
            if (response?.code() != 200) {
                downloadTask.onError(Throwable("httpError:code:${response?.code()},message:${response?.message()}"))
                cancel()
                return null
            }
            byteStream = response.body()?.byteStream()!!
            val total = response.body()?.contentLength()!!
            fos = FileOutputStream(file)
            var len: Int
            val buf = ByteArray(1024)
            var downloadSize = 0L
            while (byteStream.read(buf).apply { len = this } > 0) {
                fos.write(buf, 0, len)
                downloadSize += len
                downloadTask.setProgress(downloadSize, total, downloadSize * 100 / total)
            }
            return DownloadBean(fileName, url, file, path, total, true)
        } catch (e: Throwable) {
            if (file.exists()) {
                file.delete()
            }
            e.printStackTrace()
            return null
        } finally {
            byteStream?.close()
            fos?.flush()
            fos?.close()
        }
    }

    /**
     * 下载监听
     */
    abstract class DownListener : AsyncThread.AsyncThreadCall<Long, DownloadBean?> {
        final override fun onProgressUpdate(values: Array<out Long>) {
            super.onProgressUpdate(values)
            onProgressUpdate(values[0], values[1], values[2])
        }

        final override fun onProgressUpdateInIoThread(values: Array<out Long>) {
            super.onProgressUpdateInIoThread(values)
            onProgressUpdateInIoThread(values[0], values[1], values[2])
        }

        open fun onProgressUpdate(downloadSize: Long, totalSize: Long, progress: Long) {}
        open fun onProgressUpdateInIoThread(downloadSize: Long, totalSize: Long, progress: Long) {}
    }

    //设置了这个拦截可以直接在响应里面读取，否则会读取完网络数据才有响应
    private class ProgressResponseBody(
        val responseBody: ResponseBody,
        val onError: Function1<Throwable, Unit>
    ) : ResponseBody() {
        private var bufferedSource: BufferedSource? = null
        override
        fun contentType(): MediaType? {
            return responseBody.contentType()
        }

        override
        fun contentLength(): Long {
            return responseBody.contentLength()
        }

        override
        fun source(): BufferedSource? {
            if (bufferedSource == null) {
                bufferedSource = Okio.buffer(source(responseBody.source()))
            }
            return bufferedSource
        }

        private fun source(source: Source): Source {
            return object : ForwardingSource(source) {
                var totalBytesRead = 0L
                override fun read(sink: Buffer, byteCount: Long): Long {
                    return try {
                        val bytesRead = super.read(sink, byteCount)
                        // read() returns the number of bytes read, or -1 if this source is exhausted.
                        totalBytesRead += if (bytesRead != -1L) bytesRead else 0
//                        val total = responseBody.contentLength()
//                       progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
//                        progressListener?.invoke(totalBytesRead, total, null)
                        bytesRead
                    } catch (e: Throwable) {
                        e.printStackTrace()
                        //下载失败
                        onError.invoke(e)
                        -1
                    }
                }
            }
        }
    }
}